package com.cz.video.website.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cz.video.common.constant.CacheNames;
import com.cz.video.common.constant.ClientConstants;
import com.cz.video.common.constant.CommonConstants;
import com.cz.video.common.constant.VideoConstants;
import com.cz.video.common.core.domain.PageQuery;
import com.cz.video.common.core.page.TableDataInfo;
import com.cz.video.common.exception.ServiceException;
import com.cz.video.common.helper.LoginHelper;
import com.cz.video.common.utils.JsonUtils;
import com.cz.video.common.utils.StringUtils;
import com.cz.video.common.utils.redis.RedisUtils;
import com.cz.video.common.vod.constants.VodConstants;
import com.cz.video.common.vod.dto.VideoInfo;
import com.cz.video.common.vod.dto.VodCallbackEvent;
import com.cz.video.common.vod.dto.VodMetaInfo;
import com.cz.video.common.vod.service.VodService;
import com.cz.video.website.domain.WebsiteVideo;
import com.cz.video.website.domain.WebsiteVideoTag;
import com.cz.video.website.domain.bo.WebsiteHistoryBo;
import com.cz.video.website.domain.bo.WebsiteLikeBo;
import com.cz.video.website.domain.bo.WebsiteVideoBo;
import com.cz.video.website.domain.vo.WebsiteHistoryVo;
import com.cz.video.website.domain.vo.WebsiteVideoVo;
import com.cz.video.website.mapper.WebsiteVideoMapper;
import com.cz.video.website.mapper.WebsiteVideoTagMapper;
import com.cz.video.website.service.IWebsiteVideoService;
import com.cz.video.website.service.WebsiteHistoryService;
import com.cz.video.website.service.WebsiteLikeService;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RSet;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.event.EventListener;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 电影视频表 服务实现类
 * </p>
 *
 * @author 洪圆胜
 * @since 2023-10-15
 */
@Service
@RequiredArgsConstructor
public class WebsiteVideoServiceImpl implements IWebsiteVideoService {

    private final WebsiteVideoMapper videoMapper;
    private final WebsiteVideoTagMapper videoTagMapper;
    private final VodService vodService;
    private final WebsiteLikeService likeService;
    private final WebsiteHistoryService historyService;

    /**
     * 新增电影视频
     * @param websiteVideo
     * @return
     */
    @Override
    @Transactional
    public int addVideo(WebsiteVideoBo websiteVideo) {
        int rows = videoMapper.insert(websiteVideo);
        //新增对应的标签
        if (rows!=0){
            if (CollUtil.isNotEmpty(websiteVideo.getTagIds())){
                videoTagMapper.insertBatch(websiteVideo.getTagIds().stream()
                    .map(tagId->new WebsiteVideoTag(websiteVideo.getVideoId(),tagId)).toList());
            }
        }
        return rows;
    }

    @Override
    @Transactional
    @CacheEvict(cacheNames = CacheNames.WEBSITE_VIDEO, key = "#websiteVideo.videoId")
    public int update(WebsiteVideoBo websiteVideo) {
        websiteVideo.setUploaderId(null);
        int res = videoMapper.updateById(websiteVideo);
        //删除之前的老标签
        videoTagMapper.delete(Wrappers.<WebsiteVideoTag>lambdaQuery()
            .eq(WebsiteVideoTag::getVideoId, websiteVideo.getVideoId()));
        //新增对应的标签
        if (res!=0){
            if (CollUtil.isNotEmpty(websiteVideo.getTagIds())){
                videoTagMapper.insertBatch(websiteVideo.getTagIds().stream()
                    .map(tagId->new WebsiteVideoTag(websiteVideo.getVideoId(),tagId)).toList());
            }
        }
        return res;
    }

    @Override
    public int deleteSelfById(Long[] id) {
        return videoMapper.delete(Wrappers.<WebsiteVideo> lambdaQuery().in(WebsiteVideo::getVideoId, (Object[]) id)
            .eq(WebsiteVideo::getUploaderId,LoginHelper.getUserId()));
    }

    @Override
//    @Cacheable(cacheNames = CacheNames.WEBSITE_VIDEO, key = "#id")
    public WebsiteVideoVo getById(Long id) {
        return videoMapper.selectDetailInfo(id,LoginHelper.getUserId());
    }

    @Override
    public TableDataInfo<WebsiteVideoVo> page(WebsiteVideoBo websiteVideo, PageQuery pageQuery) {
        historyService.reflush(LoginHelper.getUserId());
        Page<WebsiteVideoVo> page= videoMapper.selectPageList(buildQueryWrapper(websiteVideo), pageQuery.build(),websiteVideo,LoginHelper.getUserId());
        //设置封面，不要问为什么在创建视频的时候不上传封面，原因就是麻烦

        setCover(page.getRecords());
        return TableDataInfo.build(page);
    }

    public void setCover(List<WebsiteVideoVo> rows) {
        if (CollUtil.isEmpty(rows))
            return;
        //设置封面
        List<VideoInfo> videoInfoList = vodService.getVideoInfoList(rows.stream()
            //如果有设置了封面则跳过
//            .filter(websiteVideoVo -> StringUtils.isBlank(websiteVideoVo.getImages()))
            .map(WebsiteVideoVo::getVid).toList());
        if (CollUtil.isNotEmpty(videoInfoList)) {
            rows.forEach(sectionVo -> {
                for (VideoInfo videoInfo : videoInfoList) {
                    if (StringUtils.equals(videoInfo.getVideoId(), sectionVo.getVid()))
                        sectionVo.setImages(videoInfo.getCoverURL());
                }
            });
        }
    }

    @Override
    public boolean recordClick(Long userId, WebsiteVideoVo websiteVideo) {
        if (userId==null|| websiteVideo ==null)
            return false;
        RSet<Object> set = RedisUtils.getClient().getSet(CacheNames.WEBSITE_CLICK + websiteVideo.getVideoId());
        //如果不存在就查一下数据库
        if (!set.isExists()){
            WebsiteHistoryBo historyBo = new WebsiteHistoryBo();
            historyBo.setVideoId(websiteVideo.getVideoId()).setUserId(userId);
            List<WebsiteHistoryVo> historyVoList = historyService.list(historyBo);
            if (CollUtil.isNotEmpty(historyVoList)){
                set.addAll(historyVoList.stream().map(WebsiteHistoryVo::getUserId).toList());
            }
        }
        if (!set.contains(userId)){
            set.add(userId);
            websiteVideo.setClickCount(websiteVideo.getClickCount()+1);
            videoMapper.updateById(websiteVideo);
            return true;
        }else {
            return false;
        }
    }

    @Override
    public TableDataInfo<WebsiteVideoVo> hotPage(PageQuery pageQuery) {
        //先这样吧
        return page(new WebsiteVideoBo(),pageQuery);
    }

    @Override
    public TableDataInfo<WebsiteVideoVo> pageRecommended(Long videoId, PageQuery pageQuery) {
        WebsiteVideoBo videoBo = new WebsiteVideoBo();
        videoBo.setVideoId(videoId);
        return page(videoBo,pageQuery);
    }

    @Override
    @Cacheable(cacheNames = CacheNames.WEBSITE_VIDEO, key = "#video.videoId")
    public int updateUploadStatus(WebsiteVideo video) {
        return videoMapper.updateById(video);
    }

    @Override
    public WebsiteVideo getByVid(String vid) {
        return videoMapper.selectOne(Wrappers.<WebsiteVideo> lambdaQuery()
                .eq(WebsiteVideo::getVid, vid)
                .eq(WebsiteVideo::getDelFlag,CommonConstants.NOT_DELETED));
    }


    @Async
    @Override
    @EventListener
    @Cacheable(cacheNames = CacheNames.WEBSITE_VIDEO, key = "#vodCallbackEvent.videoId")
    public void refreshMetaInfo(VodCallbackEvent vodCallbackEvent) {
        //等个1秒,不然太快了获取不到数据了
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        VodMetaInfo metaInfo =vodService.getMetaInfoByVid(vodCallbackEvent.getVid());
        System.out.println(JsonUtils.toJsonString(metaInfo));
        WebsiteVideo video = new WebsiteVideo();
        video.setVideoId(vodCallbackEvent.getVideoId())
                .setUploadStatus(VodConstants.UPLOAD_SUCCESS)
                .setFileSize( vodCallbackEvent.getSize())
                .setFileUrl(vodCallbackEvent.getFileUrl())
                .setTotalVideoLength(NumberUtil.parseInt(metaInfo.getDuration()));
        updateUploadStatus(video);
    }

    @Override
    public int audit(Long videoId, Boolean isPass,String auditResult) {
        WebsiteVideoVo videoVo = getById(videoId);
        if (videoVo == null) {
            throw new ServiceException("视频不存在");
        }
        if (isPass) {
            videoVo.setAuditState(VideoConstants.STATUS_PASS);
            videoVo.setStatus(VideoConstants.STATUS_ONLINE);
        }
        else videoVo.setAuditState(VideoConstants.STATUS_UN_PASS);
        videoVo.setAuditResult(auditResult);
        return videoMapper.updateById(videoVo);
    }

    @Override
    public int like(Long sourceId) {
        int res=0;
        Long userId = LoginHelper.getUserId();
        // 检查是否已点赞
        boolean like = likeService.isLike(sourceId, userId);
        //如果已点赞，则取消点赞，取消点赞时删除点赞（包含点踩）记录
        String likeCacheName = CacheNames.CLIENT_LIKE.split("#")[0];
        if (like){
            //在开始删除点赞用户id
            RedisUtils.getClient().getSet(likeCacheName +":like:"+ sourceId).remove(userId);
            //删除数据库点赞（包含点踩）记录
            res=likeService.delete(sourceId, userId);
            like(sourceId,-1);
        }else {
            //在最开始加上点赞者id
            RedisUtils.getClient().getSet(likeCacheName +":like:"+ sourceId).add(userId);
            //添加点赞记录
            WebsiteLikeBo likeBo = new WebsiteLikeBo();
            WebsiteVideo video = videoMapper.selectById(sourceId);
            if (video==null)
                throw new ServiceException("该评论不存在");
            likeBo.setSourceId(sourceId)
                .setUserId(userId)
                .setType(ClientConstants.LIKE)
                .setLikedUserId(video.getUploaderId());
            res=likeService.add(likeBo);
            like(sourceId,1);
        }
        return res;
    }

    @Override
    public int updateStatus(Long videoId, Boolean isOnline) {
        WebsiteVideo video = new WebsiteVideo();
        video.setVideoId(videoId);
        if (isOnline)
            video.setStatus(VideoConstants.STATUS_ONLINE);
        else video.setStatus(VideoConstants.STATUS_OFFLINE);
        return videoMapper.updateById(video);
    }

    //    @Override
    @Retryable(value = OptimisticLockingFailureException.class,maxAttempts = 3,backoff = @Backoff(delay = 100))
    public boolean like(Long sourceId, int i) {
        WebsiteVideo video = videoMapper.selectById(sourceId);
        video.setSupportCount(video.getSupportCount()+ i);
        // 只有手动抛出一次
        if (videoMapper.updateById(video)==0)
            throw new OptimisticLockingFailureException("系统繁忙请稍后重试");
        return true;
    }


    private Wrapper<WebsiteVideo> buildQueryWrapper(WebsiteVideoBo websiteVideoBo) {
        Map<String, Object> params = websiteVideoBo.getParams();
        QueryWrapper<WebsiteVideo> wrapper = Wrappers.query();
        wrapper.eq("wv.del_flag", CommonConstants.NOT_DELETED)
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getVideoId()), "wv.video_id", websiteVideoBo.getVideoId())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getAuditState()), "wv.audit_state", websiteVideoBo.getAuditState())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getCategoryId()), "wv.category_id", websiteVideoBo.getCategoryId())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getType()), "wv.type", websiteVideoBo.getType())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getUploaderId()), "wv.uploader_id", websiteVideoBo.getUploaderId())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getUploadStatus()), "wv.upload_status", websiteVideoBo.getUploadStatus())

            .like(StringUtils.isNotBlank(websiteVideoBo.getTitle()), "wv.title", websiteVideoBo.getTitle())
            .eq(ObjectUtil.isNotNull(websiteVideoBo.getStatus()), "wv.status", websiteVideoBo.getStatus())
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                "wv.create_time", params.get("beginTime"), params.get("endTime"))
        ;
        return wrapper;
    }
}
